﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: CategoricalDistribution.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2012-09-06 11:39 AM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using BOL.Linq.Probability;

namespace BOL.Maths.Distributions
{
    /// <summary>
    /// Represents a categorical distribution with probability vector p.
    /// This distribution is a special case of the multinomial distribution of n = 1.
    /// <see>http://en.wikipedia.org/wiki/Categorical_distribution</see>
    /// </summary>
    public class CategoricalDistribution<TKey> : NomialDistributionBase<TKey>, IUnivariateDistribution<TKey>, IEquatable<CategoricalDistribution<TKey>>
        where TKey : struct, IComparable
    {
        #region Public properties
        
        /// <summary>Gets the range of a categorical distribution.</summary>
        public IRange<TKey> Domain { get { return new Range<TKey>(ProbabilityTable.Keys.First(), ProbabilityTable.Keys.Last()); } }

        /// <summary>Gets the mean of a categorical distribution.</summary>
        public double Mean { get { throw new Exception("Mean is undefined for categorical distribution."); } }

        /// <summary>Gets the median of a categorical distribution.</summary>
        public double Median { get { throw new Exception("Median is undefined for categorical distribution."); } }

        /// <summary>Gets the mode of a categorical distribution.</summary>
        public double Mode { get { throw new Exception("Mode is undefined for categorical distribution."); } }

        /// <summary>Gets the variance of a categorical distribution.</summary>
        public double Variance { get { throw new Exception("Variance is undefined for categorical distribution."); } }

        /// <summary>Gets the skewness of a categorical distribution.</summary>
        public double Skewness { get { throw new Exception("Skewness is undefined for categorical distribution."); } }

        /// <summary>Gets the kurtosis of a categorical distribution.</summary>
        public double Kurtosis { get { throw new Exception("Kurtosis is undefined for categorical distribution."); } }

        /// <summary>Gets the entropy of a categorical distribution.</summary>
        public double Entropy { get { return ProbabilityTable.Sum(x => - x.Value * Math.Log(x.Value)); } }

        #endregion

        #region Constructors

        /// <summary>
        /// Instantiates a categorical distribution.
        /// </summary>
        /// <param name="probabilityTable">probability dictionary</param>
        public CategoricalDistribution(IDictionary<TKey, double> probabilityTable)
            : base(probabilityTable) { }

        public CategoricalDistribution(ICollection<TKey> keys)
            : this(keys.Zip(Enumerable.Repeat(1.0, keys.Count()), (k, v) => new { k, v }).ToDictionary(x => x.k, x => x.v)) { }

        #endregion

        #region ICloneable implementation

        public CategoricalDistribution<TKey> Clone()
        {
            return new CategoricalDistribution<TKey>(ProbabilityTable);
        }

        object ICloneable.Clone()
        {
            return Clone();
        }
        
        #endregion

        #region Public methods

        /// <summary>
        /// Rather than taking a vector as an pmf input for the multinomial distribution  (e.g. (0, 1, 0, 0)),
        /// directly receives the outcome index as an argument. (e.g. 1 = second outcome)
        /// </summary>
        /// <param name="value">outcome index starting from 0</param>
        /// <returns></returns>
        public double Pdf(TKey value)
        {
            return !ProbabilityTable.ContainsKey(value) ? 0.0 : ProbabilityTable[value];
        }

        /// <summary>
        /// Returns the cummulative categorical density given value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns>cummulative categorical square density</returns>
        public double Cdf(TKey value)
        {
            throw new Exception("Cdf is undefined for categorical distribution.");
        }

        /// <summary>
        /// Returns the value of the categorical distribution for given probability.
        /// </summary>
        /// <param name="p">probability</param>
        /// <returns></returns>
        public TKey Quantile(double p)
        {
            var sum = 0.0;

            foreach (var key in ProbabilityTable.OrderBy(x => x.Value).Select(x => x.Key))
            {
                sum += ProbabilityTable[key];
                if (p <= sum)
                    return key;
            }

            return default(TKey);
        }

        /// <summary>
        /// Returns a random value from the categorical distribution.
        /// </summary>
        /// <returns>random value</returns>
        public TKey Sample(Random r)
        {
            return ProbabilityTable.Sample(r);
        }

        #endregion

        #region IEquatable<TKey> implementation

        public bool Equals(CategoricalDistribution<TKey> other)
        {
            return base.Equals(other);
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override string ToString()
        {
            return String.Format("Categorical({0})", String.Join(", ", ProbabilityTable.Select(p => String.Format("p_{0} = {1:0.0000}", p.Key, p.Value))));
        }

        #endregion
    }
}
